package rgbos.forum.search;

import java.util.HashSet;
import java.util.Hashtable;

import javax.xml.parsers.*;
import javax.xml.transform.*;
import javax.xml.transform.dom.DOMSource;
import javax.xml.transform.stream.StreamResult;

import org.w3c.dom.*;

import rgbos.forum.domain.*;

public class SearchIndex {	
	private Hashtable<Integer, String> wordIdToWord_;
	private Hashtable<String, Integer> wordToWordId_;
		
	private Hashtable<Integer, HashSet<Integer>> wordIdToPostId_;

	public SearchIndex() {
		super();		
		readSearchData();
	}	
	
	/**
	 * USED ONLY BY THE SEARCH AGENT! Adds a message to the search index.
	 * @param message to be added.
	 * @return
	 */
	public boolean addPostToIndex(Post message) {
		//adds the words to the search index
		Integer tPostId = message.getId_();		
		String tPost = message.getBody_();
		String[] tWords = tPost.split(" ");
		int tCurrentId = -1;
		for (String word : tWords) {
			word = removeSpecialChars(word.toLowerCase()); //remove special chars and convert to lower case
			Integer tWordId = wordToWordId_.get(word);
			if (tWordId == null) {
				int tId = getId();
				if (tId == -1)
					return false;
				tWordId = addWord(tId,word);
				tCurrentId = tId;
			}
			addWordToPost(tWordId, tPostId);			
		}
		if (tCurrentId != -1)
			writeSearchData(tCurrentId);
		return true;
	}		

	private int getId() {
		Document tDoc = readXMLFile("searchData.xml");
		if (tDoc == null)
			return -1;
		
		NodeList tSearchData = tDoc.getElementsByTagName("SearchData");
		Node tNode = tSearchData.item(0);
		Node tAtt = tNode.getAttributes().item(0);
		int tId = Integer.valueOf(tAtt.getNodeValue());
		tAtt.setNodeValue(String.valueOf(tId++));
		
		writeSearchData(tId);
		return tId;
	}

	private String removeSpecialChars(String word) {
		while (isSpecialChar(word.charAt(word.length()-1))) {
			word = word.substring(0, word.length()-1);
		}
		return word;		
	}

	private boolean isSpecialChar(char c) {
		if ( (c >= 48 & c <=57) | (c >= 97 & c <= 122))
			return false;
		
		return true;
	}

	private int addWord(int wordId, String word) {
		wordIdToWord_.put(wordId,word);
		wordToWordId_.put(word,wordId);
		return wordId;
	}	
	
	private void addWordToPost(int wordId,int postId) {
		HashSet<Integer> tHs = wordIdToPostId_.get(wordId);
		if (tHs == null) {
			tHs = new HashSet<Integer>();			
			wordIdToPostId_.put(wordId, tHs);
		}
		tHs.add(postId);		
	}
	
	//XML PARSING AND WRITING
	
	private void readSearchData() {
//		wordIdToWord_, wordToWordId_, wordIdToPostId_;
		
		wordIdToWord_ = new Hashtable<Integer, String>();
		wordToWordId_ = new Hashtable<String, Integer>();
		wordIdToPostId_ = new Hashtable<Integer, HashSet<Integer>>();
		
		Document tDoc = this.readXMLFile("searchData.xml");
		if (tDoc == null) {
			try {				
				DocumentBuilderFactory tFactory = DocumentBuilderFactory.newInstance();
				DocumentBuilder tBuilder = tFactory.newDocumentBuilder();            

				tDoc = tBuilder.newDocument();
				
				Element tRoot = tDoc.createElement("SearchData");
				tRoot.setAttribute("id", String.valueOf(0));
				tDoc.appendChild(tRoot);
				writeXMLFile(tDoc,"searchData.xml");
				
				Auditor.Log("Created a new searchData File!",this.getClass());			
			} catch (Exception e) {
				Auditor.Log("Couldn't create a searchData File! Search is not working! Check permissions.",this.getClass());
			}
		}
		
		NodeList tWordData = tDoc.getElementsByTagName("Word");
		for (int i = 0; i < tWordData.getLength(); i++) {
			Node tNode = tWordData.item(i);
			Node tId = tNode.getAttributes().item(0);
			Node tWord = tNode.getAttributes().item(1);
			String tActualId = tId.getNodeValue();
			String tActualWord = tWord.getNodeValue();
			try {
				wordIdToWord_.put(Integer.valueOf(tActualId), tActualWord);
				wordToWordId_.put(tActualWord, Integer.valueOf(tActualId));
			} catch (NumberFormatException e) {
				Auditor.Log("The search data wasn't read correctly! (Word - WordId=" + tActualId + ")", this.getClass());
			}
		}
		
		NodeList tWordToPostData = tDoc.getElementsByTagName("WordPost");
		for (int i = 0; i < tWordToPostData.getLength(); i++) {
			Node tNode = tWordToPostData.item(i);
			Node tWord = tNode.getAttributes().item(0);			
			String tWordId = tWord.getNodeValue();
			NodeList tPostNodes = tNode.getChildNodes();
			HashSet<Integer> tPostsIdSet = new HashSet<Integer>();
			for (int j = 0; j < tPostNodes.getLength(); j++) {
				Node tPost = tPostNodes.item(j);
				Node tPostIdNode = tPost.getAttributes().item(0);
				String tPostId = tPostIdNode.getNodeValue();
				try {
					tPostsIdSet.add(Integer.valueOf(tPostId));
				} catch (NumberFormatException e) {
					Auditor.Log("The search data wasn't read correctly! (WordPost - PostId=" + tPostId + ")", this.getClass());
				}
			}
			if (tPostsIdSet.size() != 0) {
				try {
					wordIdToPostId_.put(Integer.valueOf(tWordId), tPostsIdSet);
				} catch (NumberFormatException e) {
					Auditor.Log("The search data wasn't read correctly! (WordPost - WordId=" + tWordId + ")", this.getClass());
				}
			}
		}				
	}
	
	private boolean writeSearchData(int id) {		
		
		try {
			DocumentBuilderFactory tFactory =
			DocumentBuilderFactory.newInstance();
			DocumentBuilder tBuilder = tFactory.newDocumentBuilder();
			Document tDocument = tBuilder.newDocument();
			
			Element tRoot = tDocument.createElement("SearchData");
			tRoot.setAttribute("id", String.valueOf(id));
			
			Element tWordIdToWordElement = tDocument.createElement("WordIdToWord");
			
			for (Integer wordId : wordIdToWord_.keySet()) {
				Element tWordNode = tDocument.createElement("Word");
				
				tWordNode.setAttribute("id", String.valueOf(wordId));
				tWordNode.setAttribute("word", wordIdToWord_.get(wordId));
				
				tWordIdToWordElement.appendChild(tWordNode);
			}
			
			tRoot.appendChild(tWordIdToWordElement);
			
			Element tWordIdToPostIdElement = tDocument.createElement("WordIdToPostId");
			
			for (Integer wordId : wordIdToPostId_.keySet()) {
				Element tWordNode = tDocument.createElement("WordPost");
				
				tWordNode.setAttribute("wordId", String.valueOf(wordId));
				for (Integer postId : wordIdToPostId_.get(wordId)) {
					Element tPostNode = tDocument.createElement("Post");
					tPostNode.setAttribute("postId", String.valueOf(postId));
					tWordNode.appendChild(tPostNode);
				}						
				
				tWordIdToWordElement.appendChild(tWordNode);
			}
			
			tRoot.appendChild(tWordIdToPostIdElement);
			
//			wordIdToWord_, wordToWordId_, wordIdToPostId_;
			
			tDocument.appendChild(tRoot);
			writeXMLFile(tDocument,"searchData.xml");
			
			return true;
		} catch (ParserConfigurationException e) {
			Auditor.Log("Couldn't write search data to file. (Parser Error)", this.getClass());
		} catch (Exception e) {
			Auditor.Log("Couldn't write search data to file. (Unknown)", this.getClass());
		}
		
		return false;
	}
	
	private Document readXMLFile(String filename) {                              
        try {
        	DocumentBuilderFactory factory =
            DocumentBuilderFactory.newInstance();
        	DocumentBuilder builder = factory.newDocumentBuilder();
        	Document document = builder.parse(filename);
        	return document;
        }catch(Exception e){
        	Auditor.Log("Couldn't read from the XML file.", this.getClass());        	
        }
        return null;
    }
    
	private void writeXMLFile(Node node,String filename) throws Exception {
            
        try{
        	// Prepare the DOM document for writing
        	DOMSource source = new DOMSource(node);
                 
        	// Prepare the output file                
        	StreamResult result = new StreamResult(filename);
                
            // Write the DOM document to the file
            Transformer xformer = TransformerFactory.newInstance().newTransformer();
            xformer.transform(source, result);

          } catch (TransformerConfigurationException e) {
              throw e;
          } catch (TransformerException e) {
        	  throw e;
          }
    }

	public Hashtable<Integer, String> getWordIdToWord_() {
		return wordIdToWord_;
	}

	public Hashtable<String, Integer> getWordToWordId_() {
		return wordToWordId_;
	}

	public Hashtable<Integer, HashSet<Integer>> getWordIdToPostId_() {
		return wordIdToPostId_;
	}	
}
